/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2021-2022. All rights reserved.
 */

import path from 'path';

import { FileSet, isLinux, isWindows, MetricLogType, TaskInputValue } from '@ohos/hvigor';
import fse from 'fs-extra';

import { parsingProfileName } from '../common/common-util.js';
import { BuildArtifactConst, BuildDirConst } from '../const/build-directory-const.js';
import { CommonConst, LanguageConst } from '../const/common-const.js';
import { DistroFilterHandler } from '../distribution/distro-filter-handler.js';
import { ModuleBuildProfile } from '../options/build/module-build-profile.js';
import { ModuleJson } from '../options/configure/module-json-options.js';
import { RouterMapOptions } from '../options/configure/router-map-options.js';
import { StartupOptions } from '../options/configure/startup-options.js';
import {
  CompileResourceBuildCommandType,
  getFormConfigName,
  parseProfileJson,
} from '../utils/compile-resources-util.js';
import { FileUtil } from '../utils/file-util.js';
import { getJson5Obj } from '../utils/json-util.js';
import { OhosLogger } from '../utils/log/ohos-logger.js';
import { AbstractCompileResource } from './abstract/abstract-compile-resource.js';
import { TaskNames } from './common/task-names.js';
import { ModuleTargetData } from './data/hap-task-target-data.js';
import { ProcessResource } from './process-resource.js';
import { TargetTaskService } from './service/target-task-service.js';

import Task = TaskNames.Task;
import ModuleOptObj = ModuleJson.ModuleOptObj;
import DistroFilterBuildOpt = ModuleBuildProfile.DistroFilterBuildOpt;
import RouterMapObj = RouterMapOptions.RouterMapObj;
import { InsightIntentJson } from '../options/configure/insight-intent-json-options.js';
import InsightIntentObj = InsightIntentJson.InsightIntentObj;
import { AppStartupOptions } from '../options/configure/app-startup-options.js';
import AppStartupOpt = AppStartupOptions.AppStartupOpt;
import FormsObj = ModuleJson.FormsObj;

/**
 * 编译资源
 *
 * @since 2022/1/10
 */
export class CompileResource extends AbstractCompileResource {
  protected processProfileTask: string;
  private _log: OhosLogger = OhosLogger.getLogger(CompileResource.name);

  async beforeAlwaysAction(): Promise<void> {
    this.checkStartupTaskDependencies();
    return super.beforeAlwaysAction();
  }

  declareInputs(): Map<string, TaskInputValue> {
    const map = super.declareInputs();
    const useNormalizedOHMUrl = this.targetService.getBuildOption()?.strictMode?.useNormalizedOHMUrl;
    map.set('useNormalizedOHMUrl', !!useNormalizedOHMUrl);
    return map;
  }

  declareInputFiles(): FileSet {
    const inputFiles = this.restoolConfigBuilder.inputFiles;
    const intermediatesTempRouterMapFilePath = this.pathInfo.getIntermediatesRouterMap(
      this.targetService.getRouterMapJsonFileName(this.moduleModel) ?? BuildArtifactConst.TEMP_ROUTER_MAP_FILE_NAME,
    );
    if (fse.existsSync(intermediatesTempRouterMapFilePath)) {
      const intermediatesTempRouterMapOpt: RouterMapOptions.RouterMapOpt = getJson5Obj(intermediatesTempRouterMapFilePath);
      if (intermediatesTempRouterMapOpt.routerMap?.length) {
        inputFiles.addEntry(intermediatesTempRouterMapFilePath);
      }
    }
    if (this.intermediateTempStartupFilePath && fse.existsSync(this.intermediateTempStartupFilePath)) {
      inputFiles.addEntry(this.intermediateTempStartupFilePath);
    }
    return inputFiles.addEntries([this.resConfigFilePath!], {
      isDirectory: false,
    });
  }

  declareExecutionCommand(): string {
    return this.getCommand().toString();
  }

  constructor(targetTaskService: TargetTaskService) {
    super(targetTaskService, Task.COMPILE_RESOURCE);
    this.generateSourceRDir = this.pathInfo.getGenerateSourceR();
    this.resConfigFilePath = path.resolve(this.pathInfo.getIntermediatesRes(), CommonConst.RES_CONFIG_FILE);
    this.processProfileTask = ProcessResource.name;
  }

  initTaskDepends() {
    this.declareDepends(ProcessResource.name);
  }

  protected async doTaskAction(): Promise<void> {
    // 资源编译
    await this.compilationProcess(CompileResourceBuildCommandType.FILE, this.targetData);

    // 处理资源profile
    this.processResourceProfile();
  }

  async invokeRestool(targetData: ModuleTargetData): Promise<void> {
    const subTaskName = 'create intermediate resource category';
    const subDurationEvent = this.durationEvent.createSubEvent(subTaskName, '');
    subDurationEvent.start();

    await fse.ensureDir(path.resolve(this.pathInfo.getIntermediatesRes(), 'ids_map'));

    subDurationEvent.stop();
    subDurationEvent.setLog(subTaskName, MetricLogType.INFO);

    const hosToolchainsLibDir = this.sdkInfo.getHosToolchainsLibDir();
    const previewerCommonBinDir = this.sdkInfo.getPreviewerCommonBinDir();

    const envWindows = { path: `${hosToolchainsLibDir};${previewerCommonBinDir}` };
    const envMac = {
      DYLD_FALLBACK_LIBRARY_PATH: `${process.env.DYLD_FALLBACK_LIBRARY_PATH}:${hosToolchainsLibDir}:${previewerCommonBinDir}`,
    };
    const envLinux = {
      LD_LIBRARY_PATH: `${process.env.LD_LIBRARY_PATH}:${hosToolchainsLibDir}:${previewerCommonBinDir}`,
    };
    const env = isWindows() ? envWindows : isLinux() ? envLinux : envMac;

    /**
     * 开启超纹理功能调用 restool 时，将 cwd 设为 restool 所在目录
     * 转码部件需要根据 restool 所在目录的相对路径去读取 libastcCustomizedEncode
     */
    const restoolDir = path.dirname(this.getResToolFile());

    await this.executeCommand(this.getCommand(), this._log, () => this.replaceTargetPages(targetData, this._log), { env, cwd: restoolDir });

    this.customizeDistroFilter();
    this.customizeShortcut();
    this.processRouterMap();
    this.processStartupConfig();
  }

  /**
   * 处理stage模型target定制distroFilter场景
   * target中定制distroFilter配置写入metadata->{resource文件}下
   * @protected
   */
  protected customizeDistroFilter() {
    const curTargetOpt = this.targetData.getTargetOpt();
    if (!curTargetOpt.config?.distributionFilter) {
      // target中无定制distroFilter则跳过
      return;
    }
    const distributionFilter = curTargetOpt.config.distributionFilter;

    // 读取distroFilter资源配置文件名 module.json5->module{}->metadata[]->{resource}
    const projectDir = this.service.getProjectModel().getProjectDir();
    const apiVersion = this.targetData.getApiMeta().compileSdkVersion.version;
    const moduleModel = this.service.getModuleModel();
    const moduleNodeDir = moduleModel.getModule().getNodeDir();
    const moduleJsonPath = path.resolve(projectDir, moduleNodeDir, `src/main/${CommonConst.MODULE_JSON5}`);
    const moduleJsonOpt: ModuleOptObj = getJson5Obj(moduleJsonPath);
    const metadataObjList = moduleJsonOpt.module.metadata;

    let newDistroFilterConfig: { [key: string]: DistroFilterBuildOpt } = {};
    let profileName: string | undefined;
    const distroFilterMetadataObj = DistroFilterHandler.findFirstDistroFilterConfigMetadata(projectDir, moduleModel, metadataObjList);

    let distroFilterConfigKey = '';
    if (apiVersion === 9) {
      // api9工具仅支持识别以’distroFilter‘为key的配置资源文件
      distroFilterConfigKey = 'distroFilter';
    } else if (apiVersion >= 10) {
      // api10+工具支持识别以’distributionFilter‘为key的配置资源文件,同时也兼容’distroFilter‘,前者优先
      distroFilterConfigKey = 'distributionFilter';
    }

    if (!distroFilterMetadataObj) {
      // metadata配置中不存在distroFilter相关配置,则默认生成一个ohos_module_distro_filter配置文件并同时写入metadata配置中
      const intermediatesProfileFolderPath = path.resolve(
        this.pathInfo.getModuleProductPath(),
        `intermediates/res/${curTargetOpt.name}/${BuildDirConst.RESOURCES_BASE_PROFILE_DIR}`,
      );

      // 文件名冲突处理
      const resFileNames = FileUtil.traverseFileFolder(intermediatesProfileFolderPath);
      profileName = FileUtil.uniqueFileName(resFileNames, 'ohos_module_distro_filter');
      const resModuleJsonPath = path.resolve(this.pathInfo.getModuleProductPath(), `intermediates/res/${curTargetOpt.name}/module.json`);
      const resModuleJsonObj: ModuleOptObj = getJson5Obj(resModuleJsonPath);
      if (!resModuleJsonObj.module.metadata) {
        // 若当前不存在metadata配置则在module{}内新增metadata[{}]
        Object.assign(resModuleJsonObj.module, { metadata: [{ resource: `$profile:${profileName}` }] });
      } else {
        // 若存在metadata配置则直接添加资源配置
        resModuleJsonObj.module.metadata.push({ resource: `$profile:${profileName}` });
      }

      fse.writeJSONSync(resModuleJsonPath, resModuleJsonObj);
      newDistroFilterConfig = { [distroFilterConfigKey]: distributionFilter };
    }

    if (distroFilterMetadataObj?.resource) {
      profileName = parsingProfileName(distroFilterMetadataObj.resource);
      const distroFilterResourcePath = path.resolve(projectDir, moduleNodeDir, `src/main/${BuildDirConst.RESOURCES_BASE_PROFILE_DIR}/${profileName}.json`);
      const distroFilterConfig = getJson5Obj(distroFilterResourcePath);

      // 保留当前工程模块distroFilter配置文件的key,若不存在则根据当前工程api设置key
      distroFilterConfigKey = Object.keys(distroFilterConfig)[0] ?? distroFilterConfigKey;
      newDistroFilterConfig = {
        [distroFilterConfigKey]: {
          ...distroFilterConfig.distroFilter,
          ...distributionFilter,
        },
      };
    }

    // 中间产物res目录生成对应distroFilter配置资源文件
    const intermediatesProfilePath = path.resolve(
      this.pathInfo.getModuleProductPath(),
      `intermediates/res/${curTargetOpt.name}/${BuildDirConst.RESOURCES_BASE_PROFILE_DIR}/${profileName}.json`,
    );
    if (!fse.existsSync(intermediatesProfilePath)) {
      fse.createFileSync(intermediatesProfilePath);
    }

    fse.writeJSONSync(intermediatesProfilePath, newDistroFilterConfig);
  }

  /**
   * 将中间产物资源目录下的routerMap配置修改为合并后的routerMap配置
   * @param routerMapObjList
   * @private
   */
  private writeRouterMap(routerMapObjList: RouterMapObj[]): void {
    if (!routerMapObjList || routerMapObjList.length === 0) {
      return;
    }
    const routerMapFileName = this.targetService.getRouterMapJsonFileName(this.moduleModel) ?? BuildArtifactConst.DEFAULT_ROUTER_MAP;
    const intermediateRouterMapJsonPath = path.resolve(this.targetData.getPathInfo().getIntermediatesResProfilePath(), `${routerMapFileName}.json`);
    fse.ensureFileSync(intermediateRouterMapJsonPath);
    fse.writeFileSync(intermediateRouterMapJsonPath, JSON.stringify({ routerMap: routerMapObjList }));

    const intermediateResModuleJsonPath = path.resolve(this.targetData.getPathInfo().getIntermediatesRes(), CommonConst.MODULE_JSON);
    const intermediateResModuleJsonObj: ModuleOptObj = getJson5Obj(intermediateResModuleJsonPath);
    Object.assign(intermediateResModuleJsonObj.module, { routerMap: `$profile:${routerMapFileName}` });
    fse.writeFileSync(intermediateResModuleJsonPath, JSON.stringify(intermediateResModuleJsonObj));
  }

  private processRouterMap() {
    const intermediatesTempRouterMapFilePath = this.pathInfo.getIntermediatesRouterMap(
      this.targetService.getRouterMapJsonFileName(this.moduleModel) ?? BuildArtifactConst.TEMP_ROUTER_MAP_FILE_NAME,
    );
    if (!fse.existsSync(intermediatesTempRouterMapFilePath)) {
      return;
    }
    const intermediatesTempRouterMapOpt: RouterMapOptions.RouterMapOpt = getJson5Obj(intermediatesTempRouterMapFilePath);
    const intermediatesTempRouterMapObjList = intermediatesTempRouterMapOpt.routerMap;
    if (!intermediatesTempRouterMapObjList?.length) {
      return;
    }

    intermediatesTempRouterMapObjList.forEach((routerMapObj: RouterMapObj) => {
      delete routerMapObj.moduleNodeDir;
    });
    this.writeRouterMap(intermediatesTempRouterMapObjList);
  }

  /**
   * 处理启动框架相关任务，从intermediate/startup下读取合并后的文件，处理后写入到intermediates/res下的资源文件中，如有必要还要修改res下的module.json
   *
   * @private
   */
  private processStartupConfig() {
    const startupMergedOptions: StartupOptions | undefined = getJson5Obj(this.intermediateTempStartupFilePath);
    if (startupMergedOptions === undefined) {
      return;
    }
    startupMergedOptions.startupTasks?.forEach((startupTask) => {
      delete startupTask.moduleName;
      delete startupTask.srcEntryAbsolutePath;
      delete startupTask.startupConfigPath;
    });
    startupMergedOptions.appPreloadHintStartupTasks?.forEach((appPreloadHintStartupTask) => {
      delete appPreloadHintStartupTask.moduleName;
      delete appPreloadHintStartupTask.startupConfigPath;
      delete appPreloadHintStartupTask.isRemoteByteCodeHar;
    });
    const startupResPath = path.resolve(this.targetData.getPathInfo().getIntermediatesResProfilePath(), `${this.appStartupFileName ?? 'startup_config'}.json`);
    fse.ensureFileSync(startupResPath);
    fse.writeFileSync(startupResPath, JSON.stringify(startupMergedOptions));
    if (this.appStartupPath === undefined) {
      const resModulePath = path.resolve(this.targetData.getPathInfo().getIntermediatesRes(), 'module.json');
      const resModuleObj = getJson5Obj(resModulePath);
      resModuleObj.module.appStartup = `$profile:${this.appStartupFileName ?? 'startup_config'}`;
      fse.writeFileSync(resModulePath, JSON.stringify(resModuleObj));
    }
  }

  private checkStartupTaskDependencies(){
    const startupMergedOptions: StartupOptions | undefined = getJson5Obj(this.intermediateTempStartupFilePath);
    if (startupMergedOptions === undefined) {
      return;
    }
    const [startupTaskNames, preloadStartTaskNames] = this.getAllStartupTaskNames(startupMergedOptions);
    startupMergedOptions.startupTasks?.forEach((startupTask)=> {
      if (startupTask.dependencies && startupTask.dependencies.length > 0) {
        startupTask.dependencies.forEach((dependencyName) => {
          if (!startupTaskNames.includes(dependencyName)) {
            this._log
              ._buildError(`The dependency startupTask named ${dependencyName} in module ${startupTask.moduleName} is not exist.`)
              ._detail(`Check startupConfig file of module ${startupTask.moduleName} and fix it.`)
              ._file(startupTask.startupConfigPath!)
              ._printWarn(this.moduleName);
          }
        });
      }
    });
    startupMergedOptions.appPreloadHintStartupTasks?.forEach((appPreloadHintStartupTask)=>{
      if(appPreloadHintStartupTask.dependencies && appPreloadHintStartupTask.dependencies.length > 0) {
        appPreloadHintStartupTask.dependencies.forEach((dependencyName) => {
          if (!preloadStartTaskNames.includes(dependencyName)) {
            this._log
              ._buildError(`The dependency startupTask named ${dependencyName} in module ${appPreloadHintStartupTask.moduleName} is not exist.`)
              ._detail(`Check startupConfig file of module ${appPreloadHintStartupTask.moduleName} and fix it.`)
              ._file(appPreloadHintStartupTask.startupConfigPath!)
              ._printWarn(this.moduleName);
          }
        });
      }
    });
  }

  /**
   * 从intermediates/startup中读取合并后的启动框架配置，循环遍历拿到所有startupTask和appPreloadHintStartupTask的name属性集合
   *
   * @param startupMergedOptions
   * @private
   */
  private getAllStartupTaskNames(startupMergedOptions:StartupOptions){
    const startupTaskNames:string[] = [];
    const preloadStartTaskNames:string[] = [];
    startupMergedOptions.startupTasks?.forEach((startupTask)=>{
      startupTaskNames.push(startupTask.name);
    });
    startupMergedOptions.appPreloadHintStartupTasks?.forEach((appPreloadHintStartupTask)=>{
      preloadStartTaskNames.push(appPreloadHintStartupTask.name);
    });
    return [startupTaskNames, preloadStartTaskNames];
  }


  /**
   * 处理编译后的资源profile文件
   * @private
   */
  private processResourceProfile(): void {
    const jsonFilePath = this.pathInfo.getIntermediatesProcessProfile();
    const moduleJsonObj: ModuleOptObj = getJson5Obj(jsonFilePath);

    // 处理意图框架配置文件
    this.processInsightProfile();

    // 处理启动框架配置文件
    this.processStartUpProfile(moduleJsonObj);

    // 处理卡片配置文件
    this.processWidgetProfile(moduleJsonObj);
  }

  /**
   * 编译态处理意图框架配置文件
   * @protected
   */
  protected processInsightProfile(): void {
    const insightIntentJsonPath: string = this.pathInfo.getIntermediatesResInsightIntentJsonPath();
    const insightProfileObj: InsightIntentObj = getJson5Obj(insightIntentJsonPath);
    if (!insightProfileObj?.insightIntents) {
      return;
    }

    insightProfileObj.insightIntents.forEach((insightIntent) => {
      // 意图框架配置编译态增加codeLanguage字段
      insightIntent.codeLanguage = this.targetService.verifyModuleVersionIsArkTS12() ? LanguageConst.ARKTS1_2 : LanguageConst.DEFAULT;
    });

    fse.outputJSONSync(insightIntentJsonPath, insightProfileObj, { spaces: '\t' });
  }

  /**
   * 处理启动框架配置文件
   * @param moduleJsonObj 应用配置文件module.json5对象
   * @protected
   */
  protected processStartUpProfile(moduleJsonObj: ModuleOptObj): void {
    if (!(moduleJsonObj?.module?.appStartup)) {
      return;
    }

    const startUpProfileName = parseProfileJson(moduleJsonObj.module.appStartup);
    const startUpProfilePath = path.resolve(this.pathInfo.getIntermediatesResProfilePath(), startUpProfileName);
    if (!fse.existsSync(startUpProfilePath)) {
      return;
    }

    const startUpConfigObj: AppStartupOpt = getJson5Obj(startUpProfilePath);
    if (!startUpConfigObj) {
      return;
    }

    startUpConfigObj?.startupTasks.forEach((startupTask) => {
      // 启动框架编译态增加codeLanguage字段
      this.targetService.verifyModuleVersionIsArkTS12() ?
        (startupTask.codeLanguage = LanguageConst.ARKTS1_2) :
        (startupTask.codeLanguage = LanguageConst.DEFAULT);
    });

    fse.outputJSONSync(startUpProfilePath, startUpConfigObj, { spaces: '\t' });
  }

  /**
   * 处理卡片框架配置文件
   * @param moduleJsonObj 应用配置文件module.json5对象
   * @protected
   */
  protected processWidgetProfile(moduleJsonObj: ModuleOptObj): void {
    if (!moduleJsonObj?.module?.extensionAbilities) {
      return;
    }

    const formConfigName = getFormConfigName(moduleJsonObj);
    if (!formConfigName) {
      return;
    }

    const fromConfigPath = path.resolve(this.pathInfo.getIntermediatesResProfilePath(), formConfigName);
    if (!fse.existsSync(fromConfigPath)) {
      return;
    }

    const fromConfigObj: FormsObj | undefined  = getJson5Obj(fromConfigPath);
    if (!fromConfigObj) {
      return;
    }

    fromConfigObj?.forms?.forEach((formConfig) => {
      // 卡片框架编译态增加codeLanguage字段
      this.targetService.verifyModuleVersionIsArkTS12() ?
        (formConfig.codeLanguage = LanguageConst.ARKTS1_2) :
        (formConfig.codeLanguage = LanguageConst.DEFAULT);
    });

    fse.outputJSONSync(fromConfigPath, fromConfigObj, { spaces: '\t' });
  }
}
